
using System;
using System.Xml;
using System.Collections;
using System.Globalization;
using System.Text;

namespace appbox.Reporting.RDL
{
	///<summary>
	///  Represents the report item (and Data region) for a matrix (cross-tabulation)
	///</summary>
	[Serializable]
	internal class Matrix : DataRegion
	{
		Corner _Corner;		// The region that contains the elements of
							// the upper left corner area of the matrix.
							// If omitted, no report items are output in
							// the corner.
		ColumnGroupings _ColumnGroupings;	// The set of column groupings for the matrix
		RowGroupings _RowGroupings;	// The set of row groupings for the matrix
		MatrixRows _MatrixRows;		// The rows contained in each detail cell
									// of the matrix layout
		MatrixColumns _MatrixColumns;	// The columns contained in each detail
									// cell of the matrix layout
		MatrixLayoutDirectionEnum _LayoutDirection;	// Indicates whether the matrix columns
									// grow left-to-right (with headers on the
									// left) or right-to-left (with headers on the
									// right).
		int _GroupsBeforeRowHeaders;	// The number of instances of the
									// outermost column group that should
									// appear to the left of the row headers
									// (right of the row headers for RTL
									// matrixes). Default is 0.
		string _CellDataElementName;	// The name to use for the cell element. Default: �Cell�
		MatrixCellDataElementOutputEnum _CellDataElementOutput; // Indicates whether the cell contents
									//should appear in a data rendering.  Default is Output.
		static string nullterminal = '\ufffe'.ToString();
		static string terminal = '\uffff'.ToString();

		internal Matrix(ReportDefn r, ReportLink p, XmlNode xNode):base(r,p,xNode)
		{
			_Corner=null;
			_ColumnGroupings=null;
			_RowGroupings=null;
			_MatrixRows=null;
			_MatrixColumns=null;
			_LayoutDirection=MatrixLayoutDirectionEnum.LTR;
			_GroupsBeforeRowHeaders=0;
			_CellDataElementName=null;
			_CellDataElementOutput=MatrixCellDataElementOutputEnum.Output;

			// Loop thru all the child nodes
			foreach(XmlNode xNodeLoop in xNode.ChildNodes)
			{
				if (xNodeLoop.NodeType != XmlNodeType.Element)
					continue;
				switch (xNodeLoop.Name)
				{
					case "Corner":
						_Corner = new Corner(r, this, xNodeLoop);
						break;
					case "ColumnGroupings":
						_ColumnGroupings = new ColumnGroupings(r, this, xNodeLoop);
						break;
					case "RowGroupings":
						_RowGroupings = new RowGroupings(r, this, xNodeLoop);
						break;
					case "MatrixRows":
						_MatrixRows = new MatrixRows(r, this, xNodeLoop);
						break;
					case "MatrixColumns":
						_MatrixColumns = new MatrixColumns(r, this, xNodeLoop);
						break;
					case "LayoutDirection":
						_LayoutDirection = MatrixLayoutDirection.GetStyle(xNodeLoop.InnerText, OwnerReport.rl);
						break;
					case "GroupsBeforeRowHeaders":
						_GroupsBeforeRowHeaders = XmlUtil.Integer(xNodeLoop.InnerText);
						break;
					case "CellDataElementName":
						_CellDataElementName = xNodeLoop.InnerText;
						break;
					case "CellDataElementOutput":
						_CellDataElementOutput = MatrixCellDataElementOutput.GetStyle(xNodeLoop.InnerText, OwnerReport.rl);
						break;
					default:
						if (DataRegionElement(xNodeLoop))	// try at DataRegion level
							break;
						// don't know this element - log it
						OwnerReport.rl.LogError(4, "Unknown Matrix element '" + xNodeLoop.Name + "' ignored.");
						break;
				}
			}
			DataRegionFinish();			// Tidy up the DataRegion

			if (_ColumnGroupings == null)
				OwnerReport.rl.LogError(8, "Matrix element ColumnGroupings not specified for " + (this.Name == null? "'name not specified'": this.Name.Nm));
			if (_RowGroupings == null)
				OwnerReport.rl.LogError(8, "Matrix element RowGroupings not specified for " + (this.Name == null? "'name not specified'": this.Name.Nm));
			if (_MatrixRows == null)
				OwnerReport.rl.LogError(8, "Matrix element MatrixRows not specified for " + (this.Name == null? "'name not specified'": this.Name.Nm));
			if (_MatrixColumns == null)
 				OwnerReport.rl.LogError(8, "Matrix element MatrixColumns not specified for " + (this.Name == null? "'name not specified'": this.Name.Nm));

			// MatrixCells count must be the same as the number of StaticColumns.
			//   If there are no StaticColumns it must be 1
			if (OwnerReport.rl.MaxSeverity > 4)
				return;			// don't perform this check if we've already go errors
			int mc = _MatrixRows.CellCount;	// MatrixCells
			int sc = Math.Max(1, _ColumnGroupings.StaticCount);
			if (mc != sc)
			{
				OwnerReport.rl.LogError(8, "The count of MatrixCells must be 1 or equal to the number of StaticColumns if there are any.  Matrix " + (this.Name == null? "unknown.": this.Name.Nm));
			}
			// matrix columns must also equal the static count (or 1 if no static columns)
			mc = this.CountMatrixColumns;
			if (mc != sc)
			{
				OwnerReport.rl.LogError(8, "The count of MatrixColumns must be 1 or equal to the number of StaticColumns if there are any.  Matrix " + (this.Name == null? "unknown.": this.Name.Nm));
			}
			// matrix rows must also equal the static count (or 1 if no static rows)
			int mr = this.CountMatrixRows;
			int sr = Math.Max(1, _RowGroupings.StaticCount);
			if (mr != sr)
			{
				OwnerReport.rl.LogError(8, "The count of MatrixRows must be 1 or equal to the number of StaticRows if there are any.  Matrix " + (this.Name == null? "unknown.": this.Name.Nm));
			}
		}

		override internal void FinalPass()
		{
			base.FinalPass();

			float totalHeight=0;
			if (_Corner != null)
				_Corner.FinalPass();
			if (_ColumnGroupings != null)
			{
				_ColumnGroupings.FinalPass();
				totalHeight += _ColumnGroupings.DefnHeight();
			}
			if (_RowGroupings != null)
				_RowGroupings.FinalPass();
			if (_MatrixRows != null)
			{
				_MatrixRows.FinalPass();
				totalHeight += _MatrixRows.DefnHeight();
			}
			if (_MatrixColumns != null)
				_MatrixColumns.FinalPass();

			if (this.Height == null)
			{	// Calculate a height based on the sum of the TableRows
				this.Height = new RSize(this.OwnerReport, string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}pt", totalHeight));
			}

			return;
		}

		override internal void Run(IPresent ip, Row row)
		{
			Report rpt = ip.Report();
			WorkClass wc = GetValue(rpt);
			wc.FullData = wc.Data = GetFilteredData(rpt, row);

			if (!AnyRows(ip, wc.Data))		// if no rows return
				return;					//   nothing left to do

			int maxColumns;
			int maxRows;
			MatrixCellEntry[,] matrix = RunBuild(rpt, out maxRows, out maxColumns);

			// Now run thru the rows and columns of the matrix passing the information
			//   on to the rendering engine
            int headerRows = _ColumnGroupings.Items.Count;	// number of column headers we have
            if (!ip.MatrixStart(this, matrix, row, headerRows, maxRows, maxColumns))
				return;
			for (int iRow = 0; iRow < maxRows; iRow++)
			{
				ip.MatrixRowStart(this, iRow, row);
				for (int iColumn = 0; iColumn < maxColumns; iColumn++)
				{
					MatrixCellEntry mce = matrix[iRow, iColumn];
					if (mce == null)
					{
						ip.MatrixCellStart(this, null, iRow, iColumn, row, float.MinValue, float.MinValue, 1);
						ip.MatrixCellEnd(this, null, iRow, iColumn, row);
					}
					else
					{
						wc.Data = mce.Data;		// Must set this for evaluation

						Row lrow = wc.Data.Data.Count > 0? wc.Data.Data[0]:null;
						mce.DisplayItem.SetMC(rpt, mce);	// set for use by the display item
						SetGroupingValues(rpt, mce);
												   
						ip.MatrixCellStart(this, mce.DisplayItem, iRow, iColumn, lrow, mce.Height, mce.Width, mce.ColSpan);

						mce.DisplayItem.Run(ip, lrow);
						ip.MatrixCellEnd(this, mce.DisplayItem, iRow, iColumn, lrow);
					}
				}
				ip.MatrixRowEnd(this, iRow, row);
			}
			ip.MatrixEnd(this, row);
			RemoveValue(rpt);
		}

		override internal void RunPage(Pages pgs, Row row)
		{
			Report r = pgs.Report;
			if (IsHidden(r, row))
				return;

			WorkClass wc = GetValue(r);

			wc.FullData = wc.Data = GetFilteredData(r, row);

			SetPagePositionBegin(pgs);

			if (!AnyRowsPage(pgs, wc.Data))		// if no rows return
				return;						//   nothing left to do

			int maxColumns;
			int maxRows;
			int headerRows = _ColumnGroupings.Items.Count;	// number of column headers we have
			MatrixCellEntry[,] matrix = RunBuild(r, out maxRows, out maxColumns);

			// Now run thru the rows and columns of the matrix creating the pages
			RunPageRegionBegin(pgs);
			Page p = pgs.CurrentPage;
			p.YOffset += this.RelativeY(r);

			for (int iRow = 0; iRow < maxRows; iRow++)
			{
				float h = HeightOfRow(pgs, matrix, iRow);
				if (h <= 0)		// there were no cells in row
					continue;	//     skip the row

				if (p.YOffset + h > pgs.BottomOfPage)
				{
					p = RunPageNew(pgs, p);
					// run thru the headers again
					for (int aRow = 0; aRow < headerRows; aRow++)
					{	
						RunPageColumns(pgs, wc, matrix, aRow, maxColumns);
						p.YOffset += HeightOfRow(pgs, matrix, aRow);
					}
				}
				RunPageColumns(pgs, wc, matrix, iRow, maxColumns);
				p.YOffset += h;
			}

			RunPageRegionEnd(pgs);
			SetPagePositionEnd(pgs, pgs.CurrentPage.YOffset);
			RemoveValue(r);
		}

		internal void RunReset(Report rpt)
		{
			RemoveValue(rpt);
		}

		float HeightOfRow(Pages pgs, MatrixCellEntry[,] matrix, int iRow)
		{
			Report rpt = pgs.Report;
			WorkClass wc = GetValue(rpt);

			int maxColumns = matrix.GetLength(1);
			float height=0;
			bool bResetAllHeights=false;

			// Handle the corner;  it might span rows & columns
			bool bCorner = false;
			float cornerHeight=0;
			if (iRow == 0 && matrix[0, 0] != null &&
				(this.ColumnGroupings.Items.Count > 1 ||
				this.RowGroupings.Items.Count > 1))
			{
				bCorner = true;				
			}

			for (int iCol=0; iCol < maxColumns; iCol++)
			{
				MatrixCellEntry mce = matrix[iRow, iCol];
				if (mce == null)
					continue;
				if (mce.DisplayItem is Textbox)
				{
					Textbox tb = mce.DisplayItem as Textbox;
					if (tb.CanGrow)
					{
						wc.Data = mce.Data;		// Must set this for evaluation

						Row lrow = wc.Data.Data.Count > 0? wc.Data.Data[0]:null;
						mce.DisplayItem.SetMC(rpt, mce);	// set for use by the display item
						SetGroupingValues(rpt, mce);

						float tbh = tb.RunTextCalcHeight(rpt, pgs.G, lrow);
						if (height < tbh)
						{
							if (bCorner && iCol == 0)	
							{
								cornerHeight = tbh;
							}
							else
							{
								bResetAllHeights = true;
								height = tbh;
							}
						}
					}
				}

				if (bCorner && iCol == 0)
					continue;
				if (height < mce.Height)
					height = mce.Height;
			}

			if (bResetAllHeights)	// If any text forces the row to grow; all heights must be fixed
			{
				for (int iCol=0; iCol < maxColumns; iCol++)
				{
					if (bCorner && iCol == 0)
						continue;
					MatrixCellEntry mce = matrix[iRow, iCol];
					if (mce != null)
						mce.Height = height;
				}
			}

			// Even with expansion room; we might need more space for the corner
			if (bCorner && cornerHeight > matrix[0,0].Height)
			{	// add the additional space needed to the first row's height
				float newRow0Height;
				if (ColumnGroupings.Items.Count == 1)
					newRow0Height = cornerHeight;
				else if (matrix[0,1] != null)
					newRow0Height = matrix[0,1].Height + (cornerHeight - matrix[0,0].Height);
				else
					newRow0Height = (cornerHeight - matrix[0,0].Height);
				height = newRow0Height;
				matrix[0,0].Height = cornerHeight;
				for (int iCol=1; iCol < maxColumns; iCol++)
				{
					MatrixCellEntry mce = matrix[0, iCol];
					if (mce != null)
						mce.Height = newRow0Height;
				}
			}

			return height;
		}

        internal float[] ColumnWidths(MatrixCellEntry[,] matrix, int maxColumns)
        {
            float[] widths= new float[maxColumns];

            for (int iColumn = 0; iColumn < maxColumns; iColumn++)
            {
                widths[iColumn] = WidthOfColumn(matrix, iColumn);
            }
            return widths;
        }

		float WidthOfColumn(MatrixCellEntry[,] matrix, int iCol)
		{
			int maxRows = matrix.GetLength(0);
			for (int iRow=0; iRow < maxRows; iRow++)
			{
				if (matrix[iRow, iCol] != null && matrix[iRow, iCol].ColSpan == 1)
					return matrix[iRow, iCol].Width;
			}
			return 0;
		}

		void RunPageColumns(Pages pgs, WorkClass wc, MatrixCellEntry[,] matrix, int iRow, int maxColumns)
		{
			Report rpt = pgs.Report;

			float xpos = GetOffsetCalc(pgs.Report) + LeftCalc(rpt);
			for (int iColumn = 0; iColumn < maxColumns; iColumn++)
			{
				MatrixCellEntry mce = matrix[iRow, iColumn];
					
				if (mce == null)
				{	// have a null column but we need to fill column space
					xpos += WidthOfColumn(matrix, iColumn);
					continue;
				}
				wc.Data = mce.Data;		// Must set this for evaluation

				Row lrow = wc.Data.Data.Count > 0? wc.Data.Data[0]:null;
				SetGroupingValues(rpt, mce);
				mce.DisplayItem.SetMC(rpt, mce);	// set for use by the display item
				mce.XPosition = xpos;
				mce.DisplayItem.RunPage(pgs, lrow);
				xpos += mce.Width;
				iColumn += (mce.ColSpan-1);			// skip columns already accounted for
			}
		}

		// RunBuild is used by both Matrix.Run and Chart.Run to obtain the necessary data
		//   used by their respective rendering interfaces
		internal MatrixCellEntry[,] RunBuild(Report rpt, out int numRows, out int numCols)
		{
			WorkClass wc = GetValue(rpt);
			Rows _Data = wc.Data;

			// loop thru all the data;
			//    form bitmap arrays for each unique data value of each grouping (row and column) value
			int maxColumns = _RowGroupings.Items.Count;	// maximum # of columns in matrix
									// at top we need a row per column grouping
			int maxRows = _ColumnGroupings.Items.Count;	// maximum # of rows in matrix
									// at left we need a column per row grouping

			MatrixEntry mcg = new MatrixEntry(null, "", null, _Data.Data.Count);
			_ColumnGroupings.SetME(rpt, mcg);
			mcg.FirstRow=0;
			mcg.LastRow=_Data.Data.Count-1;
			mcg.Rows = new BitArray(_Data.Data.Count, true);	// all data

			MatrixEntry mrg = new MatrixEntry(null, "", null, _Data.Data.Count);
			_RowGroupings.SetME(rpt, mrg);
			mrg.FirstRow=0;
			mrg.LastRow=_Data.Data.Count-1;
			mrg.Rows = new BitArray(_Data.Data.Count, true);		// all data

			int iRow=0;				// row counter
			foreach (Row r in _Data.Data)
			{
				// Handle the column values
				HandleColumnGrouping(rpt, wc, _Data, r, mcg, 0, iRow, ref maxColumns);

				// Handle the row values
				HandleRowGrouping(rpt, wc, _Data, r, mrg, 0, iRow, ref maxRows);

				iRow++;
			}

			// Determine how many subtotal columns are needed
			maxColumns += RunCountSubtotalColumns(rpt, wc, mcg, 0);

			// Determine how many subtotal rows are needed
			maxRows += RunCountSubtotalRows(rpt, wc, mrg, 0);

			/////
			// Build and populate the 2 dimensional table of MatrixCellEntry
			//    that constitute the matrix
			/////
			MatrixCellEntry[,] matrix = new MatrixCellEntry[maxRows, maxColumns];
	
			// Do the column headings
			int iColumn = _RowGroupings.Items.Count;
			RunColumnHeaders(rpt, wc, mcg, matrix, _Data, 0, ref iColumn, 0);

			// Do the row headings
			iRow = _ColumnGroupings.Items.Count;
			RunRowHeaders(rpt, wc, mrg, matrix, _Data, ref iRow, 0, 0);

			// Do the row/column data
			iRow = _ColumnGroupings.Items.Count;		
			RunDataRow(rpt, wc, mrg, mcg, matrix, _Data, ref iRow, _RowGroupings.Items.Count, 0);

			// Do the corner
			matrix[0, 0] = RunCorner(_Data);

			// now return the matrix data
			numRows = maxRows;
			numCols = maxColumns;
			return matrix;
		}

		int CountMatrixCells
		{
			get
			{
				MatrixRow mr = this.MatrixRows.Items[0] as MatrixRow;
				return mr.MatrixCells.Items.Count;
			}
		}

		int CountMatrixColumns
		{
			get
			{
				return this.MatrixColumns.Items.Count;
			}
		}

		int CountMatrixRows
		{
			get
			{
				return this.MatrixRows.Items.Count;
			}
		}

		ColumnGrouping LastCg 
		{
			get {  return (ColumnGrouping) (_ColumnGroupings.Items[_ColumnGroupings.Items.Count-1]); }
		}

		RowGrouping LastRg 
		{
			get {  return (RowGrouping) (_RowGroupings.Items[_RowGroupings.Items.Count-1]); }
		}

		/// <summary>
		/// Get the last (dynamic) ColumnGrouping
		/// </summary>
		/// <returns></returns>
		ColumnGrouping LastDynColumnGrouping
		{
			get 
			{
				for (int i=_ColumnGroupings.Items.Count-1; i >= 0; i--)
				{
					ColumnGrouping cg = (ColumnGrouping) (_ColumnGroupings.Items[i]);
					if (cg.StaticColumns == null)
						return cg;
				}
				return (ColumnGrouping) (_ColumnGroupings.Items[_ColumnGroupings.Items.Count-1]);
			}
		}

		/// <summary>
		/// Get the last (dynamic) RowGrouping
		/// </summary>
		/// <returns></returns>
		RowGrouping LastDynRowGrouping
		{
			get
			{
				for (int i=_RowGroupings.Items.Count-1; i >= 0; i--)
				{
					RowGrouping rg = (RowGrouping) (_RowGroupings.Items[i]);
					if (rg.StaticRows == null)
						return rg;
				}
				return (RowGrouping) (_RowGroupings.Items[_RowGroupings.Items.Count-1]);
			}
		}

		void HandleRowGrouping(Report rpt, WorkClass wc, Rows rows, Row r, MatrixEntry m, int rgi, int iRow, ref int maxRows)
		{
			while (rgi < _RowGroupings.Items.Count)
			{
				RowGrouping rg = _RowGroupings.Items[rgi] as RowGrouping;
				Grouping grp=null;
				string result;
				
				if (rg.StaticRows != null)	// handle static rows
				{
					for (int sri=0; sri < rg.StaticRows.Items.Count; sri++)
					{
						result = Convert.ToChar(Convert.ToInt32('a')+sri).ToString() + terminal;	// static row; put all data in it
						StaticRow sr = rg.StaticRows.Items[sri] as StaticRow;
						MatrixEntry ame;
                        m.HashData.TryGetValue(result, out ame);
                        if (ame == null)
						{
							ame = new MatrixEntry(r, result, m, rows.Data.Count);
							ame.RowGroup = rg;
							ame.StaticRow = sri;
							m.HashData.Add(result, ame);
							if (rg == LastRg)		// Add a row when we add data at lowest level
								maxRows++;
						}
						ame.Rows.Set(iRow, true);
						// Logic in FirstRow and Last row determine whether value gets set
						ame.FirstRow = iRow;
						ame.LastRow = iRow;
						HandleRowGrouping(rpt, wc, rows, r, ame, rgi+1, iRow, ref maxRows);
					}
					break;	// handled ones below it recursively
				}
				else							// handle dynamic columns
				{
					grp = rg.DynamicRows.Grouping;

					StringBuilder sb = new StringBuilder();
					foreach (GroupExpression ge in grp.GroupExpressions.Items)
					{
						string temp = ge.Expression.EvaluateString(rpt, r);
						if (temp == null || temp == "")
							sb.Append(nullterminal);
						else
							sb.Append(temp);
						sb.Append(terminal);		// mark end of group 
					}
					result = sb.ToString();

					MatrixEntry ame;
                    m.HashData.TryGetValue(result, out ame);
                    if (ame == null)
					{
						ame = new MatrixEntry(r, result, m, rows.Data.Count);
						ame.RowGroup = rg;
						m.HashData.Add(result, ame);
						if (rg == LastRg)		// Add a row when we add data at lowest level
							maxRows++;
					}
					ame.Rows.Set(iRow, true);
					// Logic in FirstRow and Last row determine whether value gets set
					ame.FirstRow = iRow;
					ame.LastRow = iRow;
					m = ame;			// now go down a level
					rgi++;
				}
			}
		}

		void HandleColumnGrouping(Report rpt, WorkClass wc, Rows rows, Row r, MatrixEntry m, int cgi, int iRow, ref int maxColumns)
		{
			while (cgi < _ColumnGroupings.Items.Count)
			{
				ColumnGrouping cg = _ColumnGroupings.Items[cgi] as ColumnGrouping;
				Grouping grp=null;
				string result;
				
				if (cg.StaticColumns != null)	// handle static columns
				{
					for (int sci=0; sci < cg.StaticColumns.Items.Count; sci++)
					{
						result = Convert.ToChar(Convert.ToInt32('a')+sci).ToString() + terminal;	// static column; put all data in it
						StaticColumn sc = cg.StaticColumns.Items[sci] as StaticColumn;
						MatrixEntry ame;
                        m.HashData.TryGetValue(result, out ame);
                        if (ame == null)
						{
							ame = new MatrixEntry(r, result, m, rows.Data.Count);
							ame.ColumnGroup = cg;
							ame.StaticColumn = sci;
							m.HashData.Add(result, ame);
							if (cg == LastCg)		// Add a column when we add data at lowest level
								maxColumns++;
						}
						ame.Rows.Set(iRow, true);
						// Logic in FirstRow and Last row determine whether value gets set
						ame.FirstRow = iRow;
						ame.LastRow = iRow;
						HandleColumnGrouping(rpt, wc, rows, r, ame, cgi+1, iRow, ref maxColumns);
					}
					break;	// handled ones below it recursively
				}
				else							// handle dynamic columns
				{
					grp = cg.DynamicColumns.Grouping;

					StringBuilder sb = new StringBuilder();
					foreach (GroupExpression ge in grp.GroupExpressions.Items)
					{
						string temp = ge.Expression.EvaluateString(rpt, r);
						if (temp == null || temp == "")
							sb.Append(nullterminal);
						else
							sb.Append(temp);
						sb.Append(terminal);		// mark end of group 
					}
					result = sb.ToString();

					MatrixEntry ame;
                    m.HashData.TryGetValue(result, out ame);
					if (ame == null)
					{
						ame = new MatrixEntry(r, result, m, rows.Data.Count);
						ame.ColumnGroup = cg;
						m.HashData.Add(result, ame);
						if (cg == LastCg)		// Add a column when we add data at lowest level
							maxColumns++;
					}
					ame.Rows.Set(iRow, true);
					// Logic in FirstRow and Last row determine whether value gets set
					ame.FirstRow = iRow;
					ame.LastRow = iRow;
					m = ame;			// now go down a level
					cgi++;
				}
			}
		}

		int RunCountSubtotalColumns(Report rpt, WorkClass wc, MatrixEntry m, int level)
		{
			// Get the number of static columns
			int scCount = Math.Max(1, this._ColumnGroupings.StaticCount);

			int count = 0;
			// Increase the column count when subtotal is requested at this level
			ColumnGrouping cg = (ColumnGrouping) (_ColumnGroupings.Items[level]);
			if (cg.DynamicColumns != null &&
				cg.DynamicColumns.Subtotal != null)
				count = scCount;

			if (m.GetSortedData(rpt) == null || level+1 >= _ColumnGroupings.Items.Count)		   
				return count;

			// Now dive into the data
			foreach (MatrixEntry ame in m.GetSortedData(rpt))
			{
				count += RunCountSubtotalColumns(rpt, wc, ame, level+1);
			}

			return count;
		}
		
		int RunCountSubtotalRows(Report rpt, WorkClass wc, MatrixEntry m, int level)
		{
			// Get the number of static columns
			int srCount = Math.Max(1, this._RowGroupings.StaticCount);

			int count = 0;
			// Increase the row count when subtotal is requested at this level
			RowGrouping rg = (RowGrouping) (_RowGroupings.Items[level]);
			if (rg.DynamicRows != null &&
				rg.DynamicRows.Subtotal != null)
				count = srCount;

			if (m.GetSortedData(rpt) == null || level+1 >= _RowGroupings.Items.Count)		   
				return count;

			// Now dive into the data
			foreach (MatrixEntry ame in m.GetSortedData(rpt))
			{
				count += RunCountSubtotalRows(rpt, wc, ame, level+1);
			}

			return count;
		}
		
		void RunColumnHeaders(Report rpt, WorkClass wc, MatrixEntry m, MatrixCellEntry[,] matrix, Rows _Data, int iRow, ref int iColumn, int level)
		{
			foreach (MatrixEntry ame in m.GetSortedData(rpt))
			{
				matrix[iRow, iColumn] = RunGetColumnHeader(rpt, ame, _Data);
				matrix[iRow, iColumn].Width = RunGetColumnWidth(matrix[iRow, iColumn]);
				matrix[iRow, iColumn].Height = ame.ColumnGroup.Height == null? 0: ame.ColumnGroup.Height.Points;
				if (ame.GetSortedData(rpt) != null)
				{
					RunColumnHeaders(rpt, wc, ame, matrix, _Data, iRow+1, ref iColumn, level+1);
				}
				else
					iColumn++;
			}

			ColumnGrouping cg = (ColumnGrouping) (_ColumnGroupings.Items[level]);

			// if we need subtotal on the group
			if (cg.DynamicColumns != null &&
				cg.DynamicColumns.Subtotal != null)
			{
				ReportItem ri =  cg.DynamicColumns.Subtotal.ReportItems.Items[0];	
				matrix[iRow, iColumn] = new MatrixCellEntry(_Data, ri);
				matrix[iRow, iColumn].Height = cg.Height.Points;
				matrix[iRow, iColumn].Width = RunGetColumnWidth(matrix[iRow, iColumn]);
				RunColumnStaticHeaders(rpt, wc, matrix, _Data, iRow, iColumn, level);
				iColumn += this.CountMatrixCells;
			}
		}

		void RunColumnStaticHeaders(Report rpt, WorkClass wc, MatrixCellEntry[,] matrix, Rows _Data, int iRow, int iColumn, int level)
		{
			ColumnGrouping cg=null;
			for (int i=level+1; i < _ColumnGroupings.Items.Count; i++)
			{
				iRow++;				// the row will below the headers
				cg = (ColumnGrouping) (_ColumnGroupings.Items[i]);
				if (cg.StaticColumns != null)
					break;
			} 
			if (cg == null || cg.StaticColumns == null)
				return;

			foreach (StaticColumn sc in cg.StaticColumns.Items)
			{
				ReportItem ri = sc.ReportItems.Items[0];
				matrix[iRow, iColumn] = new MatrixCellEntry(_Data, ri);
				matrix[iRow, iColumn].Height = cg.Height.Points;
				matrix[iRow, iColumn].Width = RunGetColumnWidth(matrix[iRow, iColumn]);

				iColumn++;
			}
			return;
		}

		float RunGetColumnWidth(MatrixCellEntry mce)
		{
			if (this.MatrixColumns == null)
				return 0;		// We use this routine for chart(s) and they don't build the matrix columns

			MatrixColumn mcol;	// work variable to hold a MatrixColumn

			mcol = this.MatrixColumns.Items[0] as MatrixColumn;
			float defWidth = mcol.Width.Points;

			if (CountMatrixColumns == 1)	// if only one column width is easy
				return defWidth;

			// find out which static column it is.
			ColumnGrouping cg=null;
			MatrixCells mcells=null;
			ReportItem ri = mce.DisplayItem;
			for (ReportLink rl= ri.Parent; rl != null; rl = rl.Parent)
			{
				if (rl is ColumnGrouping)
				{
					cg = rl as ColumnGrouping;
					break;
				}
				if (rl is MatrixCells)
				{
					mcells = rl as MatrixCells;
					break;
				}
				if (rl is Matrix)
					break;
			}

			int offset = 0;

			// If the item is one of the MatrixCell; then use same offset
			if (mcells != null)
			{
				foreach (MatrixCell mcell in mcells.Items)
				{
					ReportItem ric = mcell.ReportItems.Items[0] as ReportItem;
					if (ric == ri)
					{
						mcol = this.MatrixColumns.Items[offset] as MatrixColumn;
						return mcol.Width.Points;
					}
					offset++;
				}
				return defWidth;
			}

			if (cg == null || cg.StaticColumns == null)
				return defWidth;

			// Otherwise find the same relative Matrix Column from the static columns
			mcol=null;
			foreach (StaticColumn sc in cg.StaticColumns.Items)
			{
				ReportItem cri = sc.ReportItems.Items[0] as ReportItem;
				if (ri == cri)
				{
					mcol = this.MatrixColumns.Items[offset] as MatrixColumn;
					break;
				}
				offset++;
			}

			return mcol == null? defWidth: mcol.Width.Points;
		}

		MatrixCellEntry RunGetColumnHeader(Report rpt, MatrixEntry me, Rows _Data)
		{
			ReportItem ri;
			if (me.ColumnGroup.StaticColumns != null)
			{	// Handle static column reference
				StaticColumn sc = me.ColumnGroup.StaticColumns.Items[me.StaticColumn] as StaticColumn;
				ri = sc.ReportItems.Items[0];	
			}
			else
				ri = me.ColumnGroup.DynamicColumns.ReportItems.Items[0];	// dynamic column
			Rows subData = new Rows(rpt, _Data, me.FirstRow, me.LastRow, me.Rows);
			MatrixCellEntry mce = new MatrixCellEntry(subData, ri);

			return mce;
		}

		MatrixCellEntry RunCorner(Rows d)
		{
			if (_Corner == null)
				return null;

			ReportItem ri = _Corner.ReportItems.Items[0];	
			MatrixCellEntry mce = new MatrixCellEntry(d, ri);

			float height=0;
			foreach (ColumnGrouping cg in this.ColumnGroupings.Items)
			{
				height += cg.Height.Points;
			}
			mce.Height = height;

			float width=0;
			foreach (RowGrouping rg in this.RowGroupings.Items)
			{
				width += rg.Width.Points;
			}
			mce.Width = width;	

			mce.ColSpan = RowGroupings.Items.Count;
			return mce;
		}

		void RunDataColumn(Report rpt, WorkClass wc, MatrixEntry rm, MatrixEntry cm, MatrixCellEntry[,] matrix, Rows _Data, int iRow, ref int iColumn, int level, int rowcell)
		{
			BitArray andData;
			MatrixRow mr = this.MatrixRows.Items[rowcell] as MatrixRow;
			float height = mr.Height == null? 0: mr.Height.Points;

			foreach (MatrixEntry ame in cm.GetSortedData(rpt))
			{
				if (ame.ColumnGroup != LastCg)
				{
					RunDataColumn(rpt, wc, rm, ame, matrix, _Data, iRow, ref iColumn, level+1, rowcell);
					continue;
				}
				andData = new BitArray(ame.Rows);	// copy the data
				andData.And(rm.Rows);				//  because And is destructive
				matrix[iRow, iColumn] = RunGetMatrixCell(rpt, ame, iRow, _Data, andData, 
						Math.Max(rm.FirstRow, ame.FirstRow),
						Math.Min(rm.LastRow, ame.LastRow));
				matrix[iRow, iColumn].Height = height;
				matrix[iRow, iColumn].Width = RunGetColumnWidth(matrix[iRow, iColumn]);
				matrix[iRow, iColumn].ColumnME = ame;
				matrix[iRow, iColumn].RowME = rm;
				
				iColumn++;
			}
			// do we need to subtotal this?
			ColumnGrouping cg = (ColumnGrouping) (_ColumnGroupings.Items[level]);
			if (cg.DynamicColumns != null &&
				cg.DynamicColumns.Subtotal != null)
			{
				andData = new BitArray(cm.Rows);	// copy the data
				andData.And(rm.Rows);				//  because And is destructive
				for (int i=0; i < this.CountMatrixCells; i++)
				{
					matrix[iRow, iColumn] = RunGetMatrixCell(rpt, cm, rowcell, i, _Data, andData, 
						Math.Max(rm.FirstRow, cm.FirstRow),
						Math.Min(rm.LastRow, cm.LastRow));
					matrix[iRow, iColumn].Height = height;
					matrix[iRow, iColumn].Width = RunGetColumnWidth(matrix[iRow, iColumn]);
					matrix[iRow, iColumn].ColumnME = cm;
					matrix[iRow, iColumn].RowME = rm;
					iColumn++;
				}
			}
		}

		void RunDataRow(Report rpt, WorkClass wc, MatrixEntry rm, MatrixEntry cm, MatrixCellEntry[,] matrix, Rows _Data, ref int iRow, int iColumn, int level)
		{
			int saveColumn;
			int headerRows = _ColumnGroupings.Items.Count;	// number of column headers we have
			int rgsCount = this.RowGroupings.StaticCount;	// count of static row groups
			foreach (MatrixEntry ame in rm.GetSortedData(rpt))
			{
				if (ame.RowGroup != LastRg)
				{
					RunDataRow(rpt, wc, ame, cm, matrix, _Data, ref iRow, iColumn, level+1);
					continue;
				}
				saveColumn = iColumn;
				int rowcell = rgsCount == 0? 0: (iRow - headerRows) % rgsCount;
				RunDataColumn(rpt, wc, ame, cm, matrix, _Data, iRow, ref saveColumn, 0, rowcell);
				iRow++;
			}
			// do we need to subtotal this?
			RowGrouping rg = (RowGrouping) (_RowGroupings.Items[level]);
			if (rg.DynamicRows != null &&
				rg.DynamicRows.Subtotal != null)
			{
				for (int i=0; i < this.CountMatrixRows; i++)
				{
					saveColumn = iColumn;
					RunDataColumn(rpt, wc, rm, cm, matrix, _Data, iRow, ref saveColumn, 0, i);
					iRow++;
				}
			}
		}

		void RunRowHeaders(Report rpt, WorkClass wc, MatrixEntry m, MatrixCellEntry[,] matrix, Rows _Data, ref int iRow, int iColumn, int level)
		{
			foreach (MatrixEntry ame in m.GetSortedData(rpt))
			{
				matrix[iRow, iColumn] = RunGetRowHeader(rpt, ame, _Data);
				matrix[iRow, iColumn].Height = RunRowHeight(iRow);
				matrix[iRow, iColumn].Width = ame.RowGroup.Width == null? 0: ame.RowGroup.Width.Points;
				if (ame.GetSortedData(rpt) != null)
				{
					RunRowHeaders(rpt, wc, ame, matrix, _Data, ref iRow, iColumn+1, level+1);
				}
				else
					iRow++;
			}

			RowGrouping rg = (RowGrouping) (_RowGroupings.Items[level]);
			// do we need to subtotal this
			if (rg.DynamicRows != null &&
				rg.DynamicRows.Subtotal != null)
			{					   // TODO need to loop thru static??
				ReportItem ri = rg.DynamicRows.Subtotal.ReportItems.Items[0];	
				matrix[iRow, iColumn] = new MatrixCellEntry(_Data, ri);
				matrix[iRow, iColumn].Width = rg.Width.Points;
				matrix[iRow, iColumn].Height = RunRowHeight(iRow);
				RunRowStaticHeaders(rpt, wc, matrix, _Data, iRow, level);
				iRow += Math.Max(1,this.RowGroupings.StaticCount);
			}
		}

		float RunRowHeight(int iRow)
		{
			// calculate the height of this row
			int headerRows = _ColumnGroupings.Items.Count;	// number of column headers we have
			int rgsCount = this.RowGroupings.StaticCount;	// count of static row groups
			int rowcell = rgsCount == 0? 0: (iRow - headerRows) % rgsCount;
			MatrixRow mr = this.MatrixRows.Items[rowcell] as MatrixRow;	// get height
			float height = mr.Height == null? 0: mr.Height.Points;
			return height;
		}

		void RunRowStaticHeaders(Report rpt, WorkClass wc, MatrixCellEntry[,] matrix, Rows _Data, int iRow, int level)
		{
			RowGrouping rg=null;
			int i;
			int iColumn=0;
			for (i=level+1; i < _RowGroupings.Items.Count; i++)
			{
				iColumn++;				// Column for the row static headers
				rg = (RowGrouping) (_RowGroupings.Items[i]);
				if (rg.StaticRows != null)
					break;
			} 
			if (rg == null || rg.StaticRows == null)
				return;

			i=0;
			foreach (StaticRow sr in rg.StaticRows.Items)
			{
				ReportItem ri = sr.ReportItems.Items[0];
				matrix[iRow, iColumn] = new MatrixCellEntry(_Data, ri);
				matrix[iRow, iColumn].Width = rg.Width.Points;
				MatrixRow mr = this.MatrixRows.Items[i++] as MatrixRow;
				float height = mr.Height == null? 0: mr.Height.Points;
				matrix[iRow, iColumn].Height = height;

				iRow++;
			}
			return;
		}

		MatrixCellEntry RunGetRowHeader(Report rpt, MatrixEntry me, Rows _Data)
		{
			ReportItem ri;
			if (me.RowGroup.StaticRows != null)
			{	// Handle static row reference
				StaticRow sr = me.RowGroup.StaticRows.Items[me.StaticRow] as StaticRow;
				ri = sr.ReportItems.Items[0];	
			}
			else	// handle dynamic row reference
				ri = me.RowGroup.DynamicRows.ReportItems.Items[0];	
			Rows subData = new Rows(rpt, _Data, me.FirstRow, me.LastRow, me.Rows);
			MatrixCellEntry mce = new MatrixCellEntry(subData, ri);

			return mce;

		}

		MatrixCellEntry RunGetMatrixCell(Report rpt, MatrixEntry me, int iRow, Rows _Data, BitArray rows, int firstRow, int lastRow)
		{
			int headerRows = _ColumnGroupings.Items.Count;	// number of column headers we have
			int rgsCount = this.RowGroupings.StaticCount;	// count of static row groups
			int rowcell = rgsCount == 0? 0: (iRow - headerRows) % rgsCount;

			return RunGetMatrixCell(rpt, me, rowcell, me.StaticColumn, _Data, rows, firstRow, lastRow);
		}

		MatrixCellEntry RunGetMatrixCell(Report rpt, MatrixEntry me, int rcell, int ccell, Rows _Data, BitArray rows, int firstRow, int lastRow)
		{
			MatrixRow mr = this._MatrixRows.Items[rcell];
			MatrixCell mc = mr.MatrixCells.Items[ccell];
			ReportItem ri = mc.ReportItems.Items[0]; 
			Rows subData = new Rows(rpt, _Data, firstRow, lastRow, rows);
			MatrixCellEntry mce = new MatrixCellEntry(subData, ri);

			return mce;
		}

		internal Corner Corner 
		{
			get { return  _Corner; }
			set {  _Corner = value; }
		}

		internal ColumnGroupings ColumnGroupings
		{
			get { return  _ColumnGroupings; }
			set {  _ColumnGroupings = value; }
		}

		internal Rows GetMyData(Report rpt)
		{
			WorkClass wc = GetValue(rpt);
			return wc.Data;
		}

		internal void SetMyData(Report rpt, Rows data)
		{
			WorkClass wc = GetValue(rpt);
			wc.Data = data;
		}

		internal RowGroupings RowGroupings
		{
			get { return  _RowGroupings; }
			set {  _RowGroupings = value; }
		}

		internal MatrixRows MatrixRows
		{
			get { return  _MatrixRows; }
			set {  _MatrixRows = value; }
		}

		internal MatrixColumns MatrixColumns
		{
			get { return  _MatrixColumns; }
			set {  _MatrixColumns = value; }
		}

		internal MatrixLayoutDirectionEnum LayoutDirection
		{
			get { return  _LayoutDirection; }
			set {  _LayoutDirection = value; }
		}

		internal int GroupsBeforeRowHeaders
		{
			get { return  _GroupsBeforeRowHeaders; }
			set {  _GroupsBeforeRowHeaders = value; }
		}

		internal string CellDataElementName
		{
			get { return  _CellDataElementName; }
			set {  _CellDataElementName = value; }
		}

		private void SetGroupingValues(Report rpt, MatrixCellEntry mce)
		{
			WorkClass wc = GetValue(rpt);
			Rows data = wc.FullData;

			SetGroupingValuesInit(rpt, data, mce.RowME, mce.ColumnME);
			SetGroupingValuesMe(rpt, data, mce.RowME);
			SetGroupingValuesMe(rpt, data, mce.ColumnME);
		
			return;
		}

		private void SetGroupingValuesInit(Report rpt, Rows data, MatrixEntry rme, MatrixEntry cme)
		{
			// handle the column grouping
			if (cme != null)
			{
				foreach (ColumnGrouping cg in this.ColumnGroupings.Items)
				{
					if (cg.DynamicColumns != null)
						SetGrouping(rpt, cg.DynamicColumns.Grouping, cme, data);
				} 
			}
			// handle the row grouping
			if (rme != null)
			{
				foreach (RowGrouping rg in this.RowGroupings.Items)
				{
					if (rg.DynamicRows != null)
						SetGrouping(rpt, rg.DynamicRows.Grouping, rme, data);
				}
			}
		}

		private void SetGroupingValuesMe(Report rpt, Rows data, MatrixEntry me)
		{
			if (me == null)
				return;
			// handle the column grouping
			if (me.ColumnGroup != null && me.ColumnGroup.DynamicColumns != null)
				SetGrouping(rpt, me.ColumnGroup.DynamicColumns.Grouping, me, data);

			// handle the row grouping
			if (me.RowGroup != null && me.RowGroup.DynamicRows != null)
				SetGrouping(rpt, me.RowGroup.DynamicRows.Grouping, me, data);

			if (me.Parent != null)	// go up the tree??
				SetGroupingValuesMe(rpt, data, me.Parent);
		}

		private void SetGrouping(Report rpt, Grouping g, MatrixEntry me, Rows data)
		{
			if (g == null)
				return;

			if (me.Data == null)
				me.Data = new Rows(rpt, data, me.FirstRow, me.LastRow,  me.Rows);
			g.SetRows(rpt, me.Data);
		}

		internal MatrixCellDataElementOutputEnum CellDataElementOutput
		{
			get { return  _CellDataElementOutput; }
			set {  _CellDataElementOutput = value; }
		}

		private WorkClass GetValue(Report rpt)
		{
			WorkClass wc = rpt.Cache.Get(this, "wc") as WorkClass;
			if (wc == null)
			{
				wc = new WorkClass();
				rpt.Cache.Add(this, "wc", wc);
			}
			return wc;
		}

		private void RemoveValue(Report rpt)
		{
			rpt.Cache.Remove(this, "wc");
		}

		class WorkClass
		{
			internal Rows Data;
			internal Rows FullData;
			internal WorkClass()
			{
				Data=null;
				FullData=null;
			}
		}
	}
}
